home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 13316 / 13316.xpi / content / historyViewer.js < prev    next >
Text File  |  2009-10-01  |  46KB  |  1,333 lines

  1.  
  2. /* Copyright (C) 2009 Norman Solomon
  3.  * e-mail: historytree.addon@yahoo.com
  4.  * 
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the License.
  12.  */
  13.  
  14. // ***********************************************************************************
  15. // *****                                                                         *****
  16. // *****    HISTORY TREE WINDOW TOP-LEVEL JAVASCRIPT FOR "historyViewer.xul"     *****
  17. // *****   -------------------------------------------------------------------   *****
  18. // *****   1) Initializes all constants and globals used by all <script> files   *****
  19. // *****   2) Handles onLoad() and onFocus() which refreshes key arrays etc.     *****
  20. // *****   3) Handles GUI events for all <..> objects in "historyViewer.xul"     *****
  21. // *****   4) Contains shared JS functions used by other <script> JS files       *****
  22. // *****                                                                         *****
  23. // ***********************************************************************************
  24.  
  25. // All globals used by "historyViewer.xul" <script...>'s are declared below.
  26. // One global and its associated class are declared in "firefoxOverlay.js"
  27. // No other globals are declared anywhere else in the HistoryTree extension 
  28.  
  29. const CANVAS_MIN_WID = screen.availWidth - 26;      // - 26 to enforce vert scrollbar
  30. const CANVAS_MIN_HGT = screen.availHeight - 80;   // Allows for top panel height
  31. const CANVAS_BAK_COL = "rgb(38,38,38)";              // Dark grey gCanvas backcolor
  32. const CURR_TAB_COL = "rgb(255,204,170)";          // Orange - Current Tab
  33. const OPEN_TAB_COL = "rgb(255,255,180)";          // Yellow - Open Tab
  34. const CLOSED_TAB_COL = "rgb(180,190,220)";          // Blue - Closed Tab
  35.  
  36. const WH_RATIO = 0.65;        // w/h ratio for all drawm ".jpeg" images
  37. const GRID_VIEW = 1;        // Set after <image...> click that shows GridView
  38. const TREE_VIEW = 2;        // Set after <image...> click that shows TreeView
  39. const GV_HGAP = 23;            // Horizontal gap between drawn GridView images
  40. const GV_VGAP = 52;            // Vertical gap between drawn GridView images
  41.  
  42. // ------------------------------------------------------------------------------
  43. // TreeView constants
  44. const BOX_WIDTH = 142;        // Width of TreeView drawn boxes
  45. const BOX_HEIGHT = 65;        // Height of TreeView drawn boxes
  46. const BOX_BTN_WID = 21;        // Width of buttons below drawn boxes
  47. const BOX_BTN_HGT = 16;        // Height of buttons below drawn boxes
  48. const LEVEL_SPACE = (BOX_WIDTH * 0.63) * (-1); // Used in attachParent()
  49. const NODE_BORDER = 41;        // *** CRITICAL VALUE - Determines overall tree-size
  50. const BORDER_STEP = 3;        // <scale...> change multiplier (changes tree-size)
  51. const MAX_IMG_WID = 750;    // Maximum size of expanded TV node image
  52.  
  53. // TreeView mouse event button click constants
  54. const BTN_NONE = 0;            // User clicked on empty TreeView gCanvas area
  55. const BTN_CHANGE_TREE = 1;    // User clicked on expand/shrink sub-tree button
  56. const BTN_SHOW_IMAGE = 2;    // User clicked on "?" show expanded image button
  57. const BTN_OPEN_PAGE = 3;    // User clicked on node to open history page in FF
  58.  
  59. // --------------------------------------------------------------------------
  60. // Globals passed from "firefoxOverlay.js" via Application storage
  61. var gNodeList;            // HistoryNode array in "firefoxOverlay.js"
  62. var gOpenTabIDs;        // Array of TabID's that are currently open in FF
  63. var gParamArray;        // Various parameters in a App storage array
  64. var gCurrentTabID;        // TabID of currently open Tab in FF
  65. var gNodeListLoadLen;    // Used in onFocus() to check for FF history changes
  66.  
  67. // --------------------------------------------------------------------------
  68. // Globals used by both TreeView and GridView
  69. var gParentFFWin;        // FF window for which history is shown
  70. var gCanvas;            // SVG style Canvas which covers 90% of the window
  71. var gCtx;                // gCanvas '2d' context for all drawing
  72. var gCanvasScroller;    // gCanvas scroller and mouse-click detect object
  73. var gCurrentView;        // Set = TREE_VIEW or GRID_VIEW (see constants above)
  74. var gSearchIndexes;        // Array to enable consistant Find Next/Prev search
  75. var gSearchNdx;            // Current gSearchIndexes[ndx] for Find Next/Prev
  76. var gGVdispArray;        // Array holding images drawn via gGVdispInterval
  77.  
  78. // Interval timing and function exit flags
  79. var gTabLoadInterval;    // Used to clear setInterval()'s and exit GUI functions
  80. var gGVdispInterval;    // Used to clear GV setInterval() and exit GUI functions
  81. var gImgExpandInterval;    // Used to clear setInterval()'s and exit GUI functions
  82. var gTimeoutInterval;    // Used to clear setTimeout()'s and exit GUI functions
  83. var gExitWinFocusEvent;    // Exits from unwanted firing of window onFocus() event
  84.  
  85. // --------------------------------------------------------------------------
  86. // TreeView globals
  87. var gTabRoots;            // Array that stores Tab sub-tree Root TreeNode's
  88. var gTreeNodes;            // Array that stores ALL TreeNode objects (main array)
  89. var gTreeMinY;            // Only used by "treeArranger.js" plantTree()
  90. var gSubTreeSize;        // Only used by "treeArranger.js" walkSubTree()
  91.  
  92. // TreeView image expansion globals
  93. var gExpandedNode;        // TreeNode that has an expanded image/info box
  94. var gImgXY;                // Expanded image/info box top-left (x,y) on gCanvas
  95. var gInfoBoxWH;            // Expanded image/info box (w,h) on gCanvas
  96. var gNodeImageWid;        // Used to expand and then clear expanded image
  97.  
  98.  
  99. // ***********************************************************
  100. // *****                                                 *****
  101. // *****   WINDOW onLoad() AND onFocus() FUNCTIONALITY   *****
  102. // *****                                                 *****
  103. // ***********************************************************
  104.  
  105. // ========================================================
  106. // Window onLoad event listener, and associated function
  107. // ========================================================
  108. window.addEventListener("load", onLoad, false);
  109. function onLoad() 
  110. {
  111.     try
  112.     {
  113.         // Maximise this window, but leave room for user to resize it
  114.         // (takes account of current screen resolution)
  115.         this.resizeTo(screen.availWidth, screen.availHeight);
  116.         gExitWinFocusEvent = true;    // Used in onFocus() event
  117.         
  118.         // Set global FF parent window
  119.         gParentFFWin = window.opener;
  120.  
  121.         // Initialize gCanvas, making sure it fills the window
  122.         gCanvas = document.getElementById('myCanvas');
  123.         gCanvas.width = CANVAS_MIN_WID;
  124.         gCanvas.height = CANVAS_MIN_HGT;
  125.  
  126.         // Fill gCanvas with dark grey blank rectangle
  127.         gCtx = gCanvas.getContext('2d');
  128.         gCtx.fillStyle = CANVAS_BAK_COL;  // Dark grey
  129.         gCtx.fillRect(0, 0, gCanvas.width, gCanvas.height);
  130.         
  131.         // Initialize canvas scroller
  132.         var canvasBox = document.getElementById('canvasBox');
  133.         gCanvasScroller = canvasBox.boxObject.QueryInterface
  134.             (Components.interfaces.nsIScrollBoxObject);
  135.  
  136.         // Init key global vars and display history GV or TV
  137.         updateHistoryWhenAllTabsLoaded();
  138.         gExitWinFocusEvent = true;
  139.         this.focus();
  140.     }
  141.     catch (e)
  142.     {
  143.         alert("History Tree Extension - Main Window onLoad() Error");
  144.     }
  145. }
  146.  
  147. // ============================================================
  148. // Extension window onFocus listener and associated function
  149. // ============================================================
  150. window.addEventListener("focus", onFocus, false);
  151. function onFocus()
  152. {
  153.     // Update history if global exit flag not set
  154.     if (gExitWinFocusEvent)
  155.     {
  156.         gExitWinFocusEvent = false;
  157.     }
  158.     else if (gTabLoadInterval === null)
  159.     {
  160.         // Data update setInterval() is NOT running
  161.         if (firefoxHistoryChanged())
  162.         {
  163.             // FF history has changed so update gTreeNodes[] etc
  164.             updateHistoryWhenAllTabsLoaded();
  165.         }
  166.         else
  167.         {
  168.             // No additions or deletions to FF history were made, but
  169.             // user may have navigated to, or re-loaded, some pages 
  170.             gParentFFWin.historyTree.putWinParamsArrayInAppStorage();
  171.             gParamArray = Application.storage.get("paramArray", null);
  172.             gCurrentTabID = gParamArray[0];  // tabID of FF visible Tab
  173.  
  174.             // Set "Window x of y" label
  175.             setLabelText_WindowXofY();
  176.  
  177.             // Reset search <textbox...> backgroundColor
  178.             var chkSearchURL = document.getElementById("chkSearchURL");
  179.             setSearchTextBoxColor(chkSearchURL.checked);
  180.             
  181.             // Update <listbox...> descriptions and text colors
  182.             updateListboxTabRootDescriptions();
  183.  
  184.             // Redraw view, so that changed gNodeList[] data appears
  185.             if (gCurrentView === TREE_VIEW)
  186.             {
  187.                 drawTree(false, null, null);
  188.             }
  189.             else
  190.             {
  191.                 var sldDraw = document.getElementById("sldDraw");
  192.                 var numAcross = (sldDraw.max - sldDraw.value) + 3;
  193.                 drawAllImagesOnCanvas(numAcross, false);
  194.             }
  195.         }
  196.     }
  197. }
  198.  
  199. // ====================================================================
  200. // Returns true if FF history has changed since this window lost focus
  201. // ====================================================================
  202. function firefoxHistoryChanged()
  203. {
  204.     if (gNodeListLoadLen === null 
  205.      || gNodeListLoadLen !== gParentFFWin.historyTree.cNodeList.length 
  206.      || !gParentFFWin.historyTree.allTabsLoaded())
  207.     {
  208.         // Pages were added, or are being added, to FF history
  209.         return true;
  210.     }
  211.     else if (gParentFFWin.historyTree.recentFFhistoryCleared())
  212.     {
  213.         // "Tools > Clear Recent History" or similar occured
  214.         return true;
  215.     }
  216.     else
  217.     {
  218.         // Check if FF has same number of Tabs as when window lost focus
  219.         var openTabIDs = gParentFFWin.historyTree.openTabIDsList();
  220.         if (openTabIDs.length !== gOpenTabIDs.length)
  221.         {
  222.             // Tabs were opened or closed in FF
  223.             return true;
  224.         }
  225.         else
  226.         {
  227.             // Check if FF TabID list has changed
  228.             for (i = 0; i < openTabIDs.length; i++)
  229.             {
  230.                 if (openTabIDs[i] !== gOpenTabIDs[i])
  231.                 {
  232.                     // Tab list or Tab order has changed in FF
  233.                     return true;
  234.                 }
  235.             }
  236.         }
  237.     }
  238.     
  239.     // No pages were added to, or removed from, FF history
  240.     return false;
  241. }
  242.  
  243. // ====================================================================
  244. // Updates and shows history when all FF window/page data has loaded
  245. // Called from onLoad(), onFocus() and Next/Prev window button clicks
  246. // ====================================================================
  247. function updateHistoryWhenAllTabsLoaded()
  248. {
  249.     // Show "Loading..." message while FF loads window/pages
  250.     var loadMsg = "";
  251.     if (gParentFFWin.historyTree.cExtLoadProcess)
  252.         loadMsg = "Loading Firefox window history...";
  253.     else if (!gParentFFWin.historyTree.allTabsLoaded())
  254.         loadMsg = "Loading Firefox page data...";
  255.     else
  256.         loadMsg = "Updating history tree...";
  257.  
  258.     // Show "Loading..." message at top-centre of gCanvas
  259.     showBigMessageOnEmptyCanvas(loadMsg, "bold 14pt verdana");
  260.     
  261.     // Disable "historyViewer.xul" top-panel GUI controls
  262.     disableOrEnableGUIcontrols(true);
  263.  
  264.     // Start setInterval() that loads and shows history
  265.     // (delay is ALWAYS needed to stop slow, "jerky", load)
  266.     gTabLoadInterval = setInterval(loadAndShowHistory, 100);
  267. }
  268.  
  269. // ===========================================================
  270. // Loads App Storage data for FF parent window gParentFFWin,
  271. // then uses this to init key global vars and show history
  272. // ===========================================================
  273. function loadAndShowHistory()
  274. {
  275.     // Check if all FF parent window Tabs have fully loaded
  276.     if (!gParentFFWin.historyTree.allTabsLoaded() 
  277.      || gParentFFWin.historyTree.cExtLoadProcess)
  278.     {
  279.         // Some FF parent window Tab content is still loading
  280.         return;
  281.     }
  282.     else
  283.     {
  284.         // All Tabs loaded, so stop setInterval() (flag reset below)
  285.         clearInterval(gTabLoadInterval);
  286.     }
  287.  
  288.     // ------------------------------------------------
  289.     // Clear any running timeouts/intervals
  290.     clearInterval(gGVdispInterval);
  291.     gGVdispInterval = null; 
  292.     clearInterval(gImgExpandInterval);
  293.     gImgExpandInterval = null;
  294.     clearTimeout(gTimeoutInterval);
  295.     gTimeoutInterval = null;
  296.  
  297.     // Set "Window x of y" label
  298.     setLabelText_WindowXofY();
  299.  
  300.     // ------------------------------------------------
  301.     // Call "firefoxOverlay.js" method that puts data in App storage
  302.     gParentFFWin.historyTree.putHistoryDataInAppStorage();    
  303.  
  304.     // Set globals using retrieved App storage data 
  305.     gNodeList = Application.storage.get("nodeArray", null);
  306.     gOpenTabIDs = Application.storage.get("tabIDarray", null);
  307.     gParamArray = Application.storage.get("paramArray", null);
  308.     gCurrentTabID = gParamArray[0];          // tabID of FF visible Tab
  309.     gNodeListLoadLen = gNodeList.length;  // Used in onFocus()
  310.     
  311.     // Initialize other key globals
  312.     gTabRoots = new Array();
  313.     gTreeNodes = new Array();
  314.     gSearchIndexes = new Array();
  315.     gSearchNdx = -1;
  316.     gTreeMinY = 0;
  317.     gSubTreeSize = 0;
  318.     gCurrentView = TREE_VIEW;
  319.     gExpandedNode = null;
  320.     gImgXY = new Point(0, 0);
  321.     gInfoBoxWH = new Point(0, 0);
  322.     gNodeImageWid = 0;
  323.     
  324.     // ---------------------------------------------------
  325.     // Show history view or a "No history" info message
  326.     if (gNodeList.length > 0)
  327.     {
  328.         // Show history view using GUI options saved in App storage
  329.         showLoadedHistoryView();
  330.     }
  331.     else
  332.     {
  333.         // Show a message if no pages in window history *** BUT NOTE
  334.         // gTreeNodes[] and other key globals are NOT set in this case
  335.         showBigMessageOnEmptyCanvas
  336.             ("No pages in window history", "bold 14pt verdana");
  337.     }
  338.     
  339.     // Set reload completion global flag (** DO NOT do this earlier)
  340.     gTabLoadInterval = null;
  341. }
  342.  
  343. // ==================================================================
  344. // Shows FF window history using user's GUI options from App Storage
  345. // ==================================================================
  346. function showLoadedHistoryView()
  347. {
  348.     // Init all vars
  349.     var btnTreeView = document.getElementById("btnTreeView");
  350.     var btnGridView = document.getElementById("btnGridView");
  351.     var lblSliderHdr = document.getElementById("lblSliderHdr");
  352.     var sldDraw = document.getElementById("sldDraw");
  353.     var optGrpView = document.getElementById("optGrpView");
  354.     var chkSearchURL = document.getElementById("chkSearchURL");
  355.     var listSelTab = document.getElementById("listSelTab");
  356.     var viewNum;
  357.     var radioOpt;
  358.     var borderSize;
  359.  
  360.     // ------------------------------------------------------------------
  361.     // Create gTreeNodes[] & gTabRoots[] using retrieved App storage data
  362.     buildFullHistoryTree();
  363.  
  364.     // Create gSearchIndexes[] - Enabling consistant search order
  365.     createGlobalSearchIndexesArray();
  366.  
  367.     // Fill "historyViewer.xul" <listbox...> with Tab root page descs
  368.     // This also sets <listbox...> selected item = gCurrentTabID FF Tab
  369.     fillListboxWithTabRootDescriptions();
  370.  
  371.     // Re-enable "historyViewer.xul" top-panel GUI controls
  372.     disableOrEnableGUIcontrols(false);
  373.     listSelTab.focus();     // <listbox...>
  374.  
  375.     // -----------------------------------------------------------
  376.     // Setup "historyViewer.xul" GUI for GV or TV (default is TV)
  377.     gCurrentView = Application.storage.get("GVorTVbuttonVal", TREE_VIEW);
  378.     if (gCurrentView === GRID_VIEW)
  379.     {
  380.         // Set GUI controls for GridView
  381.         btnGridView.src = "images/btnGVin.gif";
  382.         btnTreeView.src = "images/btnTVout.gif";
  383.         sldDraw.value = Application.storage.get("gridViewSliderVal", 4);
  384.         lblSliderHdr.value = "Image Size";
  385.     }
  386.     else
  387.     {
  388.         // Set GUI controls for TreeView
  389.         btnTreeView.src = "images/btnTVin.gif";
  390.         btnGridView.src = "images/btnGVout.gif";    
  391.         sldDraw.value = Application.storage.get("treeViewSliderVal", 1);
  392.         lblSliderHdr.value = "Tree Spacing";
  393.  
  394.         // Reset tree-spacing using slider value
  395.         borderSize = (sldDraw.value * BORDER_STEP) + NODE_BORDER;
  396.         setBorderForAllNodes(borderSize);
  397.     }
  398.  
  399.     // -----------------------------------------------------------
  400.     // Show user's chosen quick-view (default is "Show open tabs")
  401.     if (numExtWinOpenTabs() > 0)
  402.     {
  403.         // Get user's quick-view choice from App Storage
  404.         viewNum = Application.storage.get("quickViewOptGrpVal", 0);
  405.     }
  406.     else
  407.     {
  408.         // No open Tabs - So disable open Tabs quick-view options
  409.         radioOpt = document.getElementById("optOpenTabs");
  410.         radioOpt.disabled = true;
  411.         radioOpt = document.getElementById("optOpenPages");
  412.         radioOpt.disabled = true;
  413.         viewNum = 3;    // "Show closed tabs"
  414.     }
  415.     
  416.     // Set <radiogroup...> option for quick-view, then show that quick-view
  417.     optGrpView.selectedIndex = viewNum;
  418.     showQuickView(viewNum);
  419.  
  420.     // Set search <textbox...> backColor - Found = "white", Not found = "red"
  421.     chkSearchURL.checked = Application.storage.get("searchCheckboxVal", true);
  422.     setSearchTextBoxColor(chkSearchURL.checked);
  423. }
  424.  
  425. // ==================================================================
  426. // Fills "historyViewer.xul" <listbox...> with Tab root page descs
  427. // ==================================================================
  428. function fillListboxWithTabRootDescriptions()
  429. {
  430.     // Init vars
  431.     var listSelTab = document.getElementById("listSelTab");
  432.     var listItemTxt;
  433.     var tabRoot;
  434.     var startNdx = 0;
  435.     var listItem;
  436.     var itemTxtCol;
  437.     
  438.     // -------------------------------------------
  439.     // Remove all items from the <listbox...>
  440.     while (listSelTab.itemCount > 0)
  441.         listSelTab.removeItemAt(0);
  442.  
  443.     // Fill the <listbox...> with Tab root descriptions
  444.     for (var i = 0; i < gTabRoots.length; i++)
  445.     {
  446.         // Get Tab root TreeNode object
  447.         tabRoot = gTabRoots[i];
  448.  
  449.         // Set <listbox...> item number prefix
  450.         listItemTxt = (i + 1).toString() + " - ";
  451.  
  452.         // Add web-page description from Tab root HistoryNode
  453.         listItemTxt += tabRoot.histNode.desc;
  454.  
  455.         // Add item to <listbox...> including tabID as item.value
  456.         listSelTab.appendItem(listItemTxt, tabRoot.histNode.tab);
  457.         
  458.         // Set listitem font color
  459.         if (tabRoot.histNode.tab === gCurrentTabID)
  460.             itemTxtCol = "rgb(255,80,0)";    // Orange
  461.         else if (!tabRoot.inOpenTab)
  462.             itemTxtCol = "rgb(61,110,255)";     // Blue
  463.         else
  464.             itemTxtCol = "black";
  465.  
  466.         listItem = listSelTab.getItemAtIndex(i);
  467.         listItem.style.color = itemTxtCol;
  468.  
  469.         // Set <listbox...>.selectedIndex (see *** NOTE below)
  470.         if (tabRoot.histNode.tab === gCurrentTabID)
  471.             startNdx = i;
  472.     }
  473.  
  474.     // ---------------------------------------------------------------
  475.     // Show selected <listbox...> item. This seems like a kludge, but
  476.     // <listbox...> needs time to populate before item can be selected
  477.     listSelTab.ensureIndexIsVisible(startNdx);
  478.     listSelTab.selectedIndex = startNdx;
  479.     gTimeoutInterval = setTimeout
  480.         (setListboxSelectedItem, listSelTab.itemCount * 15, startNdx);
  481. }
  482.  
  483. // ============================================================
  484. // Sets <listbox...> selected item after calculated time delay
  485. // ============================================================
  486. function setListboxSelectedItem(startNdx)
  487. {
  488.     // Stop setTimeout() and set global flag
  489.     clearTimeout(gTimeoutInterval);
  490.     gTimeoutInterval = null;
  491.     
  492.     // Highlight selected <listbox...> item
  493.     var listSelTab = document.getElementById("listSelTab");
  494.     listSelTab.ensureIndexIsVisible(startNdx);
  495.     listSelTab.selectedIndex = startNdx;
  496. }
  497.  
  498. // ==========================================================
  499. // Updates "historyViewer.xul" <listbox...> listItem labels
  500. // ==========================================================
  501. function updateListboxTabRootDescriptions()
  502. {
  503.     // Init vars
  504.     var listSelTab = document.getElementById("listSelTab");
  505.     var tabRoot;
  506.     var listItem;
  507.     var listItemTxt;
  508.     var itemTxtCol;
  509.     
  510.     // --------------------------------------------------
  511.     // Update <listbox...> Tab root descriptions
  512.     for (var i = 0; i < gTabRoots.length; i++)
  513.     {
  514.         // Make sure <listbox...> item exists
  515.         if (listSelTab.itemCount > i)
  516.         {
  517.             // Set listItem label prefix
  518.             tabRoot = gTabRoots[i];
  519.             listItemTxt = (i + 1).toString() + " - ";
  520.                 
  521.             // Update listItem.label
  522.             listItem = listSelTab.getItemAtIndex(i);
  523.             listItem.label = listItemTxt + tabRoot.histNode.desc;
  524.  
  525.             // Update listitem font color
  526.             if (tabRoot.histNode.tab === gCurrentTabID)
  527.                 itemTxtCol = "rgb(255,80,0)";    // Orange
  528.             else if (!tabRoot.inOpenTab)
  529.                 itemTxtCol = "rgb(61,110,255)";     // Blue
  530.             else
  531.                 itemTxtCol = "black";
  532.  
  533.             // Set listitem font color
  534.             listItem.style.color = itemTxtCol;
  535.         }
  536.     }
  537. }
  538.  
  539. // ****************************************************************
  540. // *****                                                      *****
  541. // *****              EVENT HANDLING PROCEDURES               *****
  542. // *****         USED BY BOTH TREE-VIEW AND GRID-VIEW         *****
  543. // *****                                                      *****
  544. // ****************************************************************
  545.  
  546. // =================================================================
  547. // Returns true if invalid setInterval() or setTimeout() is running
  548. // Prevents use of this window's GUI controls during timed proceses
  549. // =================================================================
  550. function setIntervalOrTimeoutRunning()
  551. {
  552.     if (gTabLoadInterval !== null || gImgExpandInterval !== null 
  553.      || gTimeoutInterval !== null)
  554.     {
  555.         // This window's setInterval() or setTimeout() is running
  556.         return true;
  557.     }
  558.     else
  559.     {
  560.         // No window timed events are occuring
  561.         return false;
  562.     }
  563. }
  564.  
  565. // ================================================================
  566. // Called from TWO "historyViewer.xul" <image...> onclick() events
  567. // ================================================================
  568. function reloadButtonEventHandler(event)
  569. {
  570.     // Exit if a setInterval() or setTimeout() is running
  571.     if (setIntervalOrTimeoutRunning() || gGVdispInterval !== null)
  572.         return;
  573.  
  574.     // Init vars
  575.     var btnNextWin, btnPrevWin;
  576.  
  577.     if (event.target.id == "btnNextWin")
  578.     {
  579.         // Show Next FF win button briefly pushed in
  580.         btnNextWin = document.getElementById("btnNextWin");
  581.         btnNextWin.src = "images/btnNextWinIn.gif";
  582.  
  583.         // Load history for Next FF window
  584.         gTimeoutInterval = setTimeout
  585.             (loadHistoryForNextOrPrevFFwindow, 100, true);
  586.     }
  587.     else
  588.     {
  589.         // Show Previous FF win button briefly pushed in
  590.         btnPrevWin = document.getElementById("btnPrevWin");
  591.         btnPrevWin.src = "images/btnPrevWinIn.gif";
  592.  
  593.         // Load history for Previous FF window
  594.         gTimeoutInterval = setTimeout
  595.             (loadHistoryForNextOrPrevFFwindow, 100, false);
  596.     }    
  597. }
  598.  
  599. // ========================================================
  600. // Called via setTimeout() from reloadButtonEventHandler()
  601. // ========================================================
  602. function loadHistoryForNextOrPrevFFwindow(nextWin)
  603. {
  604.     // Init vars
  605.     var btnNextWin, btnPrevWin;
  606.  
  607.     // Stop setTimeout() and set global flag
  608.     clearTimeout(gTimeoutInterval);
  609.     gTimeoutInterval = null;
  610.  
  611.     // Show Next/Previous FF win button pushed out
  612.     if (nextWin)
  613.     {
  614.         btnNextWin = document.getElementById("btnNextWin");
  615.         btnNextWin.src = "images/btnNextWinOut.gif";
  616.     }
  617.     else
  618.     {
  619.         btnPrevWin = document.getElementById("btnPrevWin");
  620.         btnPrevWin.src = "images/btnPrevWinOut.gif";
  621.     }
  622.     
  623.     // Set this windows opener as Next/Prev open FF window
  624.     gParentFFWin = nextOrPrevFFwindow(nextWin);
  625.     gParentFFWin.historyTree.cExtHistTreeWin = this;
  626.  
  627.     // Rebuild key global vars and re-display GV or TV
  628.     updateHistoryWhenAllTabsLoaded();
  629. }
  630.  
  631. // ===============================================================
  632. // Called from TWO "historyViewer.xul <image...> onclick() events
  633. // ===============================================================
  634. function searchButtonEventHandler(event)
  635. {
  636.     // Exit if a setInterval() or setTimeout() is running
  637.     if (setIntervalOrTimeoutRunning() || gGVdispInterval !== null 
  638.      || gNodeList.length === 0)
  639.         return;
  640.  
  641.     // ------------------------------------------------------
  642.     // Init vars
  643.     var txtFind, chkSearchURL;
  644.     var btnFindNext, btnFindPrev;
  645.     var txtToFind;
  646.     var searchURL;
  647.  
  648.     txtFind = document.getElementById("txtFind");
  649.     txtToFind = (txtFind.value).toUpperCase();    
  650.     chkSearchURL = document.getElementById("chkSearchURL");
  651.     searchURL = chkSearchURL.checked;
  652.  
  653.     // Perform search if <textbox...> text is found
  654.     if (txtToFind !== "" && searchTextFound(txtToFind, searchURL))
  655.     {
  656.         if (event.target.id === "btnFindNext")
  657.         {
  658.             // Find Next page with matching <textbox...> text
  659.             btnFindNext = document.getElementById("btnFindNext");
  660.             btnFindNext.src = "images/btnNextIn.gif";
  661.             gTimeoutInterval = setTimeout
  662.                 (searchTree, 100, true, txtToFind, searchURL);
  663.         }
  664.         else
  665.         {
  666.             // Find Prev page with matching <textbox...> text
  667.             btnFindPrev = document.getElementById("btnFindPrev");
  668.             btnFindPrev.src = "images/btnPrevIn.gif";
  669.             gTimeoutInterval = setTimeout
  670.                 (searchTree, 100, false, txtToFind, searchURL);
  671.         }
  672.     }
  673. }
  674.  
  675. // ============================================================
  676. // Called from "historyViewer.xul" <textbox ...> onkeyup event
  677. // ============================================================
  678. function searchTextboxEventHandler(event)
  679. {
  680.     // Make <textbox...> background red if text is not found
  681.     var chkSearchURL = document.getElementById("chkSearchURL");
  682.     if (!chkSearchURL.disabled)
  683.         setSearchTextBoxColor(chkSearchURL.checked);
  684. }
  685.  
  686. // =============================================================
  687. // Called from "historyViewer.xul" <checkbox ...> onclick event
  688. // =============================================================
  689. function searchCheckboxEventHandler(event)
  690. {
  691.     // Do nothing if <checkbox...> is disabled
  692.     if (!event.target.disabled)
  693.     {
  694.         // Make <textbox...> background red if text is not found
  695.         var checked = !event.target.checked;
  696.         Application.storage.set("searchCheckboxVal", checked);
  697.         setSearchTextBoxColor(checked);
  698.     }
  699. }
  700.  
  701. // ===============================================================
  702. // Called from TWO "historyViewer.xul <image...> onclick() events
  703. // ===============================================================
  704. function imageButtonClickEventHandler(event)
  705. {
  706.     // Exit if a setInterval() or setTimeout() is running
  707.     if (setIntervalOrTimeoutRunning() || gNodeList.length === 0)
  708.         return;
  709.  
  710.     // Get "historyViewer.xul" controls and init vars
  711.     var btnGridView = document.getElementById("btnGridView");
  712.     var btnTreeView = document.getElementById("btnTreeView");
  713.     var sldDraw = document.getElementById("sldDraw");
  714.     var lblSliderHdr = document.getElementById("lblSliderHdr");
  715.     var optGrpView = document.getElementById("optGrpView");
  716.     var borderSize = 0;
  717.  
  718.     // --------------------------------------------
  719.     if (event.target.id === "btnGridView" 
  720.      && btnGridView.src === "images/btnGVout.gif")
  721.     // --------------------------------------------
  722.     {
  723.         // Change to GridView and save it to App Storage
  724.         gCurrentView = GRID_VIEW;
  725.         Application.storage.set("GVorTVbuttonVal", GRID_VIEW);
  726.  
  727.         // Change GUI controls for GridView
  728.         btnGridView.src = "images/btnGVin.gif";
  729.         btnTreeView.src = "images/btnTVout.gif";
  730.         
  731.         // Set <scale...> (slider) attributes
  732.         sldDraw.value = Application.storage.get("gridViewSliderVal", 4);
  733.         lblSliderHdr.value = "Image Size";
  734.  
  735.         // Draw all ".jpeg" images on the gCanvas
  736.         var numAcross = (sldDraw.max - sldDraw.value) + 3;
  737.         drawAllImagesOnCanvas(numAcross, true);
  738.     }
  739.     
  740.     // --------------------------------------------
  741.     if (event.target.id === "btnTreeView" 
  742.      && btnTreeView.src === "images/btnTVout.gif")
  743.     // --------------------------------------------
  744.     {
  745.         // Change to TreeView and save it to App Storage
  746.         gCurrentView = TREE_VIEW;
  747.         Application.storage.set("GVorTVbuttonVal", TREE_VIEW);
  748.  
  749.         // Change GUI controls for TreeView
  750.         btnTreeView.src = "images/btnTVin.gif";
  751.         btnGridView.src = "images/btnGVout.gif";
  752.         
  753.         // Set <scale...> (slider) attributes
  754.         sldDraw.value = Application.storage.get("treeViewSliderVal", 1);
  755.         lblSliderHdr.value = "Tree Spacing";
  756.  
  757.         // Reset tree-spacing using slider value
  758.         borderSize = (sldDraw.value * BORDER_STEP) + NODE_BORDER;    
  759.         setBorderForAllNodes(borderSize);
  760.  
  761.         // Draw tree, scrolling as required
  762.         if (optGrpView.selectedIndex === 0)
  763.             // "Show open tabs"
  764.             drawTree(false, gCurrentTabID, null);
  765.         else
  766.             // Other quick-views
  767.             drawTree(true, null, null);
  768.     }
  769. }
  770.  
  771. // ====================================================================
  772. // Called from "historyViewer.xul <scale...> onmouseup() and onkeyup()
  773. // ====================================================================
  774. function sliderChangeEventHandler(event)
  775. {
  776.     // Exit if a setInterval() or setTimeout() is running
  777.     if (setIntervalOrTimeoutRunning() || gNodeList.length === 0)
  778.         return;
  779.  
  780.     // Clear the gCanvas
  781.     gCtx.fillStyle = CANVAS_BAK_COL;  // Dark grey
  782.     gCtx.fillRect(0, 0, gCanvas.width, gCanvas.height);
  783.  
  784.     // Draw all nodes on gCanvas depending on current view
  785.     var sliderVal = event.target.value;
  786.     if (gCurrentView === GRID_VIEW)
  787.     {
  788.         // Draw all images on the gCanvas
  789.         Application.storage.set("gridViewSliderVal", sliderVal);
  790.         var numAcross = (event.target.max - sliderVal) + 3;
  791.         drawAllImagesOnCanvas(numAcross, true);
  792.     }
  793.     else
  794.     {
  795.         // Reset tree-spacing then redraw tree
  796.         Application.storage.set("treeViewSliderVal", sliderVal);
  797.         var borderSize = (sliderVal * BORDER_STEP) + NODE_BORDER;
  798.         setBorderForAllNodes(borderSize);
  799.         drawTree(false, null, null);
  800.     }
  801. }
  802.  
  803. // =========================================================
  804. // Called from "historyViewer.xul" <listbox ...> onclick()
  805. // Redraws FULL Tab sub-tree (collapsed sub-trees reappear)
  806. // =========================================================
  807. function listboxClickEventHandler(event)
  808. {
  809.     // Exit if a setInterval() or setTimeout() is running
  810.     if (setIntervalOrTimeoutRunning() || gNodeList.length === 0)
  811.         return;
  812.  
  813.     // Set <radiogroup ...> option for "Show selected tab" quick-view
  814.     var optGrpView = document.getElementById("optGrpView");
  815.     optGrpView.selectedIndex = 2;
  816.     Application.storage.set("quickViewOptGrpVal", 2);
  817.  
  818.     // Show the "Show selected tab" quick-view
  819.     showQuickView(2);
  820. }
  821.  
  822. // =================================================================
  823. // Shows selected quick-view - Called from FOUR "historyViewer.xul" 
  824. // <radio...> onclick() events and from <radiogroup...> onkeyup()
  825. // =================================================================
  826. function quickViewOptEventHandler(optNum)
  827. {
  828.     // Exit if a setInterval() or setTimeout() is running
  829.     if (setIntervalOrTimeoutRunning() || gNodeList.length === 0)
  830.         return;
  831.  
  832.     // Get optNum from <radiogroup...> if its onkeyup() fired
  833.     var optGrpView = document.getElementById("optGrpView");
  834.     if (optNum === -1)
  835.         optNum = optGrpView.selectedIndex;
  836.  
  837.     // Show GV or TV quick-view for selected option
  838.     optGrpView.selectedIndex = optNum;    // Needed
  839.     Application.storage.set("quickViewOptGrpVal", optNum);
  840.     showQuickView(optNum);
  841. }
  842.  
  843. // ==============================================================
  844. // Shows GV or TV quick-view for passed <radiogroup...> optNum
  845. // ==============================================================
  846. function showQuickView(optNum)
  847. {
  848.     // Set TreeNode pointers for passed quick-view optNum
  849.     setTreeNodePointersForSelectedQuickView(optNum);
  850.  
  851.     // Draw the required GV or TV quick-view on gCanvas
  852.     if (gCurrentView === TREE_VIEW)
  853.     {
  854.         if (optNum === 0)
  855.             // TV "Show open tabs"
  856.             drawTree(false, gCurrentTabID, null);
  857.         else
  858.             // TV Other quick-views
  859.             drawTree(true, null, null);
  860.     }
  861.     else
  862.     {
  863.         // Draw GV quick-view using req number of images per row
  864.         var sldDraw = document.getElementById("sldDraw");
  865.         var numAcross = (sldDraw.max - sldDraw.value) + 3;
  866.         drawAllImagesOnCanvas(numAcross, true);
  867.     }
  868. }
  869.  
  870. // ==============================================================
  871. // Sets TreeNode pointers for passed optNum - BUT does not draw
  872. // ==============================================================
  873. function setTreeNodePointersForSelectedQuickView(optNum)
  874. {
  875.     // Init vars
  876.     var listSelTab;     // <listbox...>
  877.     var selItem;     // <listbox...> item
  878.  
  879.     // -----------------------------------------------------
  880.     // Set TreeNode pointers for passed quick-view optNum
  881.     if (optNum === 0)
  882.     {
  883.         // "Show open tabs"
  884.         setTreePointersFor_OpenTabsQuickView();
  885.     }
  886.     else if (optNum === 1)
  887.     {
  888.         // "Show open pages"
  889.         setTreePointersFor_OpenPagesQuickView();
  890.     }
  891.     else if (optNum === 2)
  892.     {
  893.         // "Show selected tab" using <listbox...> item tabID
  894.         listSelTab = document.getElementById("listSelTab");
  895.         selItem = listSelTab.getSelectedItem(0);
  896.         setTreePointersFor_SelectedTabQuickView(selItem.value);
  897.     }
  898.     else if (optNum === 3)
  899.     {
  900.         // "Show closed tabs"
  901.         setTreePointersFor_ClosedTabsQuickView();
  902.     }
  903. }
  904.  
  905. // ===============================================================
  906. // Called when user clicks mouse on gCanvas in either GV or TV
  907. // ===============================================================
  908. function canvasMouseClickEventHandler(event)
  909. {
  910.     // Exit if a setInterval() or setTimeout() is running
  911.     if (setIntervalOrTimeoutRunning() || gGVdispInterval !== null 
  912.      || gNodeList.length === 0)
  913.         return;
  914.  
  915.     // Get current <scrollbox...> scrollbars (x, y) positions
  916.     var xs = {};    // object
  917.     var ys = {};    // object
  918.     gCanvasScroller.getPosition(xs, ys);
  919.  
  920.     // Get mouse click (x, y) adjusting for scrollbars (x, y)
  921.     // where (0, 0) is top-left corner of window NOT gCanvas
  922.     mouseX = event.clientX + xs.value;
  923.     mouseY = event.clientY + ys.value - gCanvas.offsetTop;
  924.  
  925.     // ----------------------------------------------------
  926.     // Do required action depending on the current view
  927.     if (gCurrentView === GRID_VIEW)
  928.     {
  929.         // Get TreeNode for GV image user clicked on (if any)
  930.         var tNode = gridViewTreeNodeFromMouseXY(mouseX, mouseY);
  931.         
  932.         // Open page in FF if user clicked on GV ".jpeg" image
  933.         if (tNode !== null)
  934.             openOrGotoSelectedHistoryPage(tNode);
  935.     }
  936.     else // TREE_VIEW
  937.     {
  938.         // Call "treeViewChanger.js" TV mouse-click handler
  939.         respondToTreeViewMouseClickOnCanvas(mouseX, mouseY);
  940.     }
  941. }
  942.  
  943. // ****************************************************
  944. // *****                                          *****
  945. // *****              UTILITY FUNCTIONS           *****
  946. // *****   USED BY "historyViewer.xul" JS FILES   *****
  947. // *****                                          *****
  948. // ****************************************************
  949.  
  950. // =========================================================
  951. // Called from "historyViewer.js" or "treeViewChanger.js"
  952. // when user clicks on a gCanvas TV TreeNode or GV ".jpeg" 
  953. // =========================================================
  954. function openOrGotoSelectedHistoryPage(tNode)
  955. {
  956.     // Show goto/open page modal Dialog window
  957.     if (tNode !== null)
  958.     {
  959.         // Put App Storage data used by the window opened below
  960.         Application.storage.set("openPageTreeNode", tNode);
  961.         
  962.         // Define a resizable dialog window with no max/min/close buttons and
  963.         // no right-click context menu, which will open modally at center screen
  964.         var chromeFeatures = "chrome,centerscreen,modal=yes,resizable=yes,close=no";
  965.         var winPath = "chrome://historyTree/content/openPageDialog.xul";
  966.         window.openDialog(winPath,"pageOpener",chromeFeatures);
  967.     }
  968. }
  969.  
  970. // =============================================================
  971. // ONLY called from modal window "openPageDialog.js" onUnload()
  972. // =============================================================
  973. function showHistoryPageInFirefox(optNum, selTabID)
  974. {
  975.     // Check if user is opening a new page in FF
  976.     if (optNum !== 0)
  977.     {
  978.         // Set global flags that will force full data update/refresh when
  979.         // this window's onFocus() event fires (focus is changed below)
  980.         gExitWinFocusEvent = false;
  981.         gNodeListLoadLen = null;
  982.     }
  983.  
  984.     // Set focus to current parent FF window (MUST be done first)
  985.     gParentFFWin.focus();
  986.  
  987.     // Call "firefoxOverlay.js" method to open/goto history page
  988.     var tNode = Application.storage.get("openPageTreeNode", null);
  989.     if (tNode !== null)
  990.         gParentFFWin.historyTree.openOrGotoHistoryPage(tNode, optNum, selTabID);
  991. }
  992.  
  993. // ============================================================
  994. // Returns Next/Previous open FF window using cyclic retrieval
  995. // ============================================================
  996. function nextOrPrevFFwindow(nextWin)
  997. {
  998.     var wm;                        // nsIWindowMediator
  999.     var enum;                    // Simple nsI enumerator
  1000.     var win, reqWin;            // Open FF windows 
  1001.     var winList = new Array();    // List of open FF windows
  1002.     var currWinNum;                // Integer
  1003.  
  1004.     // ---------------------------------------------------
  1005.     // Create array list of all open FF windows
  1006.     wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1007.         .getService(Components.interfaces.nsIWindowMediator);
  1008.     enum = wm.getEnumerator("navigator:browser");
  1009.  
  1010.     while (enum.hasMoreElements()) 
  1011.     {
  1012.         win = enum.getNext();
  1013.         winList.push(win);
  1014.         if (win === gParentFFWin)
  1015.             currWinNum = winList.length - 1;
  1016.     }
  1017.     
  1018.     // ---------------------------------------------------
  1019.     // Get Next/Prev open FF window using cyclic retrieval
  1020.     if (nextWin)
  1021.     {
  1022.         if (currWinNum === winList.length - 1)
  1023.             reqWin = winList[0];
  1024.         else
  1025.             reqWin = winList[currWinNum + 1];
  1026.     }
  1027.     else
  1028.     {
  1029.         if (currWinNum === 0)
  1030.             reqWin = winList[winList.length - 1];
  1031.         else
  1032.             reqWin = winList[currWinNum - 1];
  1033.     }
  1034.  
  1035.     // Clear temp array then return Next/Prev FF win
  1036.     winList.splice(0);
  1037.     return reqWin;
  1038. }
  1039.  
  1040. // ===================================================================
  1041. // Set "Window x of y" label text - Called via onLoad() and onFocus()
  1042. // ===================================================================
  1043. function setLabelText_WindowXofY()
  1044. {
  1045.     var wm;                    // nsIWindowMediator
  1046.     var enum;                // Simple nsI enumerator
  1047.     var win;                // Open FF window 
  1048.     var currWinNum, numWin;    // Integers
  1049.  
  1050.     // ------------------------------------------------
  1051.     // Get list of all open FF windows
  1052.     wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  1053.         .getService(Components.interfaces.nsIWindowMediator);
  1054.     enum = wm.getEnumerator("navigator:browser");
  1055.  
  1056.     // Get numbers used for label
  1057.     numWin = 0;
  1058.     while (enum.hasMoreElements()) 
  1059.     {
  1060.         numWin ++;
  1061.         win = enum.getNext();
  1062.         if (win === gParentFFWin)
  1063.             currWinNum = numWin;
  1064.     }
  1065.     
  1066.     // Set "Window x of y" label text
  1067.     var lblWinInfo = document.getElementById("lblWinInfo");
  1068.     lblWinInfo.value = "Window " + currWinNum.toString() 
  1069.                      + " of " + numWin.toString();
  1070. }
  1071.  
  1072. // =================================================================
  1073. // Disables or enables GUI controls while setInterval() running etc
  1074. // =================================================================
  1075. function disableOrEnableGUIcontrols(disable)
  1076. {
  1077.     // Don't re-enable if no FF history
  1078.     if (!disable && gNodeList.length === 0)
  1079.         return;
  1080.  
  1081.     // Get references to "historyViewer.xul" GUI controls
  1082.     var optGrpView = document.getElementById("optGrpView");
  1083.     var listSelTab = document.getElementById("listSelTab");
  1084.     var txtFind = document.getElementById("txtFind");
  1085.     var chkSearchURL = document.getElementById("chkSearchURL");
  1086.     var sldDraw = document.getElementById("sldDraw");
  1087.  
  1088.     // Remove items from <listbox...> if disabling
  1089.     if (disable)
  1090.     {
  1091.         while (listSelTab.itemCount > 0)
  1092.             listSelTab.removeItemAt(0);
  1093.     }
  1094.     
  1095.     // Set search <textbox...> backcolor
  1096.     if (disable)
  1097.         txtFind.style.backgroundColor = "#cccccc";    // Grey
  1098.     else
  1099.         txtFind.style.backgroundColor = "#ffffff";    // White
  1100.  
  1101.     // Disable or enable other GUI controls
  1102.     optGrpView.disabled = disable;
  1103.     listSelTab.disabled = disable;
  1104.     chkSearchURL.disabled = disable;
  1105.     sldDraw.disabled = disable;
  1106. }
  1107.  
  1108. // ==============================================================
  1109. // Loads node ".jpeg" from its dataURL, then draws it on gCanvas 
  1110. // This is only done ONCE for each node (to speed up redraws)
  1111. // ==============================================================
  1112. function drawAndUpdateNodeImage(node, x, y, w, h)
  1113. {
  1114.     // Try to draw the node's ".jpeg" image on the gCanvas
  1115.     try
  1116.     {
  1117.         // Create node ".jpeg", then draw it on gCanvas *** NOTE
  1118.         // Many tests showed this to be the most reliable approach
  1119.         node = nodeWithImage(node);
  1120.         gCtx.drawImage(node.imgToDraw, x, y, w, h);        
  1121.     }
  1122.     catch (e)
  1123.     {
  1124.         // Nothing will be drawn on gCanvas
  1125.         node.imgData = null;
  1126.     }
  1127. }
  1128.  
  1129. // =====================================================
  1130. // Returns passed hNode with its ".jpeg" image loaded
  1131. // =====================================================
  1132. function nodeWithImage(hNode)
  1133. {
  1134.     var img = new Image();
  1135.     
  1136.     // Wait until the image loads
  1137.     img.onload = function() 
  1138.     {
  1139.         // Return node with image props set
  1140.         hNode.imgToDraw = img;
  1141.         hNode.imgCreated = true;
  1142.         return hNode;
  1143.     };
  1144.     
  1145.     // This MUST be done here or sometimes onload() is NOT called
  1146.     img.src = hNode.imgData;
  1147. }
  1148.  
  1149. // ==============================================================
  1150. // Returns true if a blank rectangle is drawn instead of ".jpeg"
  1151. // ==============================================================
  1152. function blankRectangleDrawn(hNode, x, y, w, h, roundRect, bordCol)
  1153. {
  1154.     // Init integers
  1155.     var suffixPos;
  1156.     var fontSize;
  1157.     var reqWid, txtWid;
  1158.     var textX, textY, yAddOn;
  1159.     var rectMsg;   // String
  1160.  
  1161.     // -------------------------------------------
  1162.     // Check if a blank rectangle should be drawn
  1163.     suffixPos = hNode.uri.length - 4;
  1164.  
  1165.     if (hNode.imgData === null 
  1166.      || hNode.uri.lastIndexOf(".pdf") === suffixPos)
  1167.     {
  1168.         rectMsg = "Image not available";
  1169.     }
  1170.     else
  1171.     {
  1172.         return false;
  1173.     }
  1174.     
  1175.     // -------------------------------------------
  1176.     // Draw a white rectangle with border
  1177.     if (roundRect)
  1178.     {
  1179.         // Rounded corner rect with highlighted border
  1180.         gCtx.lineWidth = 3;
  1181.         drawRoundedRectWithBorder
  1182.             (x - 2, y - 2, w + 4, h + 4, 10, "white", bordCol);
  1183.     }
  1184.     else
  1185.     {
  1186.         // Square rectangle with black border
  1187.         drawBorderedRect
  1188.             (x - 1, y - 1, w + 2, h + 2, "white", bordCol, 2);
  1189.     }
  1190.     
  1191.     // ---------------------------------------------
  1192.     // Init while() loop font/text sizing vars
  1193.     reqWid = Math.round(w * 0.6);
  1194.     fontSize = 7;
  1195.     gCtx.mozTextStyle = "bold " + fontSize.toString() + "pt verdana";
  1196.     txtWid = gCtx.mozMeasureText(rectMsg);
  1197.  
  1198.     // Keep increasing font size until rectMsg reaches reqWid
  1199.     while (txtWid < reqWid && fontSize < 14)
  1200.     {
  1201.         fontSize ++;
  1202.         gCtx.mozTextStyle = "bold " + fontSize.toString() + "pt verdana";
  1203.         txtWid = gCtx.mozMeasureText(rectMsg);
  1204.     }
  1205.  
  1206.     // Draw sized rectMsg text inside the rectangle
  1207.     textX = Math.round(x + (w - txtWid) / 2);
  1208.     textY = Math.round(y + (h / 2));
  1209.     yAddOn = Math.ceil((14 - fontSize) / 2);
  1210.     gCtx.fillStyle = "rgb(190,190,190)";    // Light grey
  1211.     fillText(rectMsg, textX, textY + yAddOn);
  1212.  
  1213.     // -------------------------------------------
  1214.     // White rectangle WAS drawn
  1215.     return true;
  1216. }
  1217.  
  1218. // ============================================================
  1219. // Returns number of open Tab header TreeNode's in gTabRoots[]
  1220. // ------------------------------------------------------------
  1221. // *** NOTE - Blank FF Tabs are not shown AT ALL in GV or TV
  1222. // ============================================================
  1223. function numExtWinOpenTabs()
  1224. {
  1225.     // Get count of open Tab TreeNode's in gTabRoots[]
  1226.     var tabRoot;
  1227.     var numOpen = 0;
  1228.     for (var ndx = 0; ndx < gTabRoots.length; ndx++)
  1229.     {
  1230.         tabRoot = gTabRoots[ndx];
  1231.         if (tabRoot.inOpenTab)
  1232.             numOpen ++;
  1233.     }
  1234.     // Return the count
  1235.     return numOpen;    
  1236. }
  1237.  
  1238. // ================================================================
  1239. // Use Mozilla method to draw text on the gCanvas
  1240. // *** NOTE - This allows easy find/replace changeover for FF 3.5
  1241. //              mozTextStyle can also be globally replced with font()
  1242. // ================================================================
  1243. function fillText(text, x, y)
  1244. {
  1245.     gCtx.translate(x, y);
  1246.     gCtx.mozDrawText(text);
  1247.     gCtx.translate((-1 * x), (-1 * y));
  1248.     //gCtx.fillRect(1,1,1,1);
  1249. }
  1250.  
  1251. // ======================================================
  1252. // Clears gCanvas, then shows passed msg in passed font
  1253. // ======================================================
  1254. function showBigMessageOnEmptyCanvas(msg, font)
  1255. {
  1256.     // Init vars
  1257.     var textX;        // Integer
  1258.     var xs = {};    // object
  1259.     var ys = {};    // object
  1260.  
  1261.     // --------------------------------------------
  1262.     // Clear the gCanvas
  1263.     gCtx.fillStyle = CANVAS_BAK_COL;  // Dark grey
  1264.     gCtx.fillRect(0, 0, gCanvas.width, gCanvas.height);
  1265.  
  1266.     // Calculate x co-ordinate for drawing passed msg
  1267.     gCtx.mozTextStyle = font;
  1268.     textX = Math.round((this.innerWidth - gCtx.mozMeasureText(msg)) / 2);
  1269.     if (textX < 0)
  1270.         textX = 0;
  1271.     
  1272.     // Show "Loading..." message centre screen
  1273.     gCanvasScroller.getPosition(xs, ys);
  1274.     gCtx.fillStyle = "white";
  1275.     fillText(msg, xs.value + textX, ys.value + 85);
  1276. }
  1277.  
  1278. // =======================================================
  1279. // Returns a "trimmed" version of the passed string
  1280. // =======================================================
  1281. function trim(s)
  1282. {
  1283.     s = s.replace( /^\s*/, ""); // Trims leading spaces
  1284.     s = s.replace( /\s*$/, ""); // Trims trailing spaces
  1285.     return s;
  1286. }
  1287.  
  1288. // =======================================================
  1289. // Returns a "truncated" version of passed string
  1290. // =======================================================
  1291. function truncatedText(text, reqWidth)
  1292. {
  1293.     text = trim(text);    // Trim passed text
  1294.     var truncText = text;
  1295.     var len = text.length;
  1296.  
  1297.     // Keep chopping chars off end of text until its short enough
  1298.     while(gCtx.mozMeasureText(truncText) > reqWidth && truncText.length > 0)
  1299.     {
  1300.         len--;
  1301.         truncText = text.substring(0, len);
  1302.     }
  1303.  
  1304.     // Return truncated string or original if not truncated
  1305.     truncText = trim(truncText);
  1306.     if (truncText.length < text.length)
  1307.         return truncText.substring(0, len - 1) + "...";
  1308.     else 
  1309.         return text;
  1310. }
  1311.  
  1312. // =========================================================
  1313. // ******   TEST FUNCTION   ******
  1314. // Returns a string describing properties of passed obj
  1315. // =========================================================
  1316. function showPropertiesOf(obj)
  1317. {
  1318.     var rowTot = 0;
  1319.     var propList = "";
  1320.  
  1321.     for (var prop in obj)
  1322.     { 
  1323.         propList += prop + " = " + obj[prop] + "....";
  1324.         rowTot ++;
  1325.         if (rowTot > 4)
  1326.         {
  1327.             rowTot = 0;
  1328.             propList += "\n";
  1329.         }
  1330.     } 
  1331.     return propList;
  1332. }
  1333.